﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Layout;
using System;

using CommandLists.Model;
using CommonClasses.Utility;
using System.IO;

namespace CommandLists.Controls
{
    /// <summary>
    /// Контрол, представляющий список команд в GUI.
    /// </summary>
    public partial class CommandListControl : UserControl
    {
        #region Private fields
        //номер такта, перед которым стоит курсор редактирования
        private int editingTact = -1;

        //флаг, показывающий, редактируется ли данный список команд или нет
        private bool isEditing = false;

        #endregion

        #region Public properties

        private CommandList comList = null;

        /// <summary>
        /// Список команд, представленный данным контролом
        /// </summary>
        [Browsable(false)]
        public CommandList CommandList 
        {
            get
            {
                return comList;
            }
            private set
            {
                comList = value;
            }
        }

        /// <summary>
        /// Ширина заголовочной части контрола
        /// </summary>
        public int TitleWidth
        {
            get
            {
                return MainSplitContainer.SplitterDistance;
            }
        }
        #endregion

        public CommandListControl()
        {
            InitializeComponent();            
        }

        #region Public methods
        
        /// <summary>
        /// Вставить такт по указанному номеру и заполнить его данной командой.
        /// </summary>
        /// <param name="number">Номер такта, в который необходимо вставить новый.</param>
        /// <param name="source">Команда, которой необходимо заполнить данный такт.</param>
        public void InsertTact(int number, Command source)
        {
            CommandList.InsertTact(number, source);
        }

        /// <summary>
        /// Вставить такт по указанному номеру и заполнить его паузой.
        /// </summary>
        /// <param name="number">Номер такта, в который необходимо вставить новый.</param>        
        public void InsertEmptyTact(int number)
        {
            CommandList.InsertEmptyTact(number);

            if (ElementsNumberChanged != null)
                ElementsNumberChanged(this, new EventArgs());
        }

        /// <summary>
        /// Заменить команду в указанном такте на переданную.
        /// </summary>
        /// <param name="number">Номер такта, в котором производится установка команды</param>
        /// <param name="newCommand">Новая команда</param>
        public void SetCommandForTact(int number, Command newCommand)
        {
            CommandList.SetCommandForTact(number, newCommand);
        }

        /// <summary>
        /// Установить для данного контрола список команд.
        /// </summary>
        /// <param name="list">Список команд</param>
        public void SetCommandList(CommandList list)
        {
            if (list == null)
                throw new NullReferenceException("CommandListControl: " + CommandList.Name
                    + ". Trying to set null CommandList");

            if (CommandList != null)
            {
                DetachEventHandlers();
                CommandsPanel.Controls.Clear();
            }

            CommandList = list;

            try
            {
                PathUtility.RestoreCurrentDirectory(Locality.ExeFile);
                CommandListImage.Image = new Bitmap(CommandList.ImagePath);
                    //PathUtility.MakePathGlobal(CommandList.ImagePath,
                        //Path.GetDirectoryName(Application.ExecutablePath)));
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("CommandListControl: " + CommandList.Name
                    + ". Wrong ImagePath!", ex);
            }

            //было решено не отображать имя СК
            CommandListNameLabel.Text = string.Empty;//CommandList.Name;

            AttachEventHandlers();

            if (CommandList.CommandSequence.Count == 0)
            {
                InsertEmptyTact(0);
            }
            else            
                for (int i = 0; i < CommandList.CommandSequence.Count; i++)
                {
                    CommandsPanel.Controls.InsertAtIndex(
                      new CommandControl(CommandList.CommandSequence[i]), i);
                }

            this.Enabled = true;
        }

        /// <summary>
        /// Отключить данный контрол от списка команд.
        /// </summary>
        public void DetachFromCommandList()
        {
            DetachEventHandlers();
            this.Enabled = false;
            CommandList = null;
        }

        /// <summary>
        /// Удалить такт с указанным номером.
        /// </summary>
        /// <param name="number">Номер такта.</param>
        public void DeleteTact(int number)
        {
            CommandList.DeleteTact(number);

            if (ElementsNumberChanged != null)
                ElementsNumberChanged(this, new EventArgs());
        }

        /// <summary>
        /// Очистить все такты
        /// </summary>
        public void ClearAllTacts()
        {
            int count = CommandList.CommandSequence.Count;
            for (int i = 0; i < count; i++)
                DeleteTact(0);
        }

        /// <summary>
        /// Установить такт редактирования.
        /// </summary>
        /// <param name="number">Номер такта</param>
        public void SetEditingTact(int number)
        {
            if (!CheckIndexStrictly(number))
                throw new IndexOutOfRangeException("CommandListControl: " + CommandList.Name
                    + ". Tact number is out of range: " + number.ToString() + ".");

            editingTact = number;
            Refresh();
        }

        /// <summary>
        /// Активировать для редактирования данный список команд или деактивировать.
        /// </summary>
        /// <param name="isediting">true - если редактируется данный список команд</param>
        public void SetIsEditing(bool isediting)
        {
            isEditing = isediting;
            this.Refresh();
        }

        /// <summary>
        /// Подсветить команду (нарисовать рамку)
        /// </summary>
        /// <param name="color">Цвет, которым будет нарисована рамка</param>
        /// <param name="tact">Номер такта, на котором будет выделена команда</param>
        public void Highlight(Color color, int tact)
        {
            if ((tact >= 0) && (tact < CommandList.CommandSequence.Count))
                (CommandsPanel.Controls[tact] as CommandControl).Highlight(color);
        }

        /// <summary>
        /// Снять подсветку с команды.
        /// </summary>
        /// <param name="tact">Номер такта, на котором будет выделена команда</param>
        public void HighLightOff(int tact)
        {
            if ((tact >= 0) && (tact < CommandList.CommandSequence.Count))
                (CommandsPanel.Controls[tact] as CommandControl).HighLightOff();
        }
        
        /// <summary>
        /// Разрешить или запретить редактирвоать параметры команд этого СК
        /// </summary>
        /// <param name="canEdit">true - если можно</param>
        public void SetCommandParamsEditingEnabled(bool canEdit)
        {
            foreach(CommandControl ctrl in CommandsPanel.Controls)
                ctrl.SetCanShowMenu(canEdit);
        }
        #endregion

        #region Scroll realization

        private int firstVisibleTact = 0;
        private bool isNeedScrollPrev = false;

        /// <summary>
        /// Номер первого такта, который будет отображаться.
        /// </summary>
        [Browsable(false)]
        public int FirstVisibleTact
        {
            get
            {
                return firstVisibleTact;
            }
            set
            {
                if (CommandList == null) return;

                if (!CheckIndexStrictly(value))
                    throw new IndexOutOfRangeException("CommandListControl: " + CommandList.Name
                        + ". Tact number is out if range: " + value.ToString() + ".");

                if (!IsNeedScroll)
                {
                    firstVisibleTact = 0;
                    return;
                }

                firstVisibleTact = value;

                RethinkVisibility();

                Refresh();
            }
        }
        
        /// <summary>
        /// Показывает, необходимо ли включить полосу прокрутки.
        /// </summary>
        [Browsable(false)]
        public bool IsNeedScroll
        {
            get
            {
                return (VisibleElementsCount < CommandsPanel.Controls.Count);
            }
        }

        /// <summary>
        /// Количество элементов, способных одновременно уместиться на контроле.
        /// </summary>
        [Browsable(false)]
        public int VisibleElementsCount
        {
            get
            {
                return (CommandsPanel.Width + 2) / CommandsPanel.Height;
            }
        }
        
        /// <summary>
        /// Вызывается при изменении необходимости полосы прокрутки для данного контрола
        /// </summary>
        public event EventHandler NeedScrollChanged;

        /// <summary>
        /// Вызывактся при изменении длины данного списка команд.
        /// </summary>
        public event EventHandler ElementsNumberChanged;

        private void RethinkScroll()
        {
            if (IsNeedScroll != isNeedScrollPrev)
            {
                if (NeedScrollChanged != null)
                    NeedScrollChanged(this, new EventArgs());
                isNeedScrollPrev = IsNeedScroll;
            }

            //количество видимых контролов-команд
            var viscount =
                (IsNeedScroll) ?
                    VisibleElementsCount
                : CommandList.CommandSequence.Count;

            var newvalue = CommandList.CommandSequence.Count - viscount;
            if ((FirstVisibleTact + viscount) > CommandList.CommandSequence.Count)
                FirstVisibleTact = (newvalue > 0) ? newvalue : 0;
        }
            
        private void RethinkVisibility()
        {
            //количество видимых контролов-команд
            var viscount =
                (IsNeedScroll) ?
                    VisibleElementsCount
                : CommandList.CommandSequence.Count;

            //меняем видимость контролов
            for (int i = 0; i < CommandsPanel.Controls.Count; i++)
                CommandsPanel.Controls[i].Visible = (i >= FirstVisibleTact);
        }

        #endregion

        #region Private methods

        private void AttachEventHandlers()
        {
            CommandList.OnCommandChanged += new CommandSequenceChangedHandler(CommandList_OnCommandChanged);
            CommandList.OnTactDeleted += new CommandSequenceChangedHandler(CommandList_OnTactDeleted);
            CommandList.OnTactInserted += new CommandSequenceChangedHandler(CommandList_OnTactInserted);
            CommandsPanel.ControlAdded += new ControlEventHandler(CommandsPanel_ControlAdded);
        }

        private void DetachEventHandlers()
        {
            CommandList.OnCommandChanged -= new CommandSequenceChangedHandler(CommandList_OnCommandChanged);
            CommandList.OnTactDeleted -= new CommandSequenceChangedHandler(CommandList_OnTactDeleted);
            CommandList.OnTactInserted -= new CommandSequenceChangedHandler(CommandList_OnTactInserted);
            CommandsPanel.ControlAdded -= new ControlEventHandler(CommandsPanel_ControlAdded);
        }

        private bool CheckIndexStrictly(int number)
        {
            return (number >= 0) && (number < CommandList.CommandSequence.Count);
        }

        #endregion

        #region Event handlers
        private void CommandListControl_Resize(object sender, EventArgs e)
        {
            //изменяем размеры картинки списка команд, сохраняя место для подписи
            LeftSplitContainer.SplitterDistance = LeftSplitContainer.Height;
            MainSplitContainer.SplitterDistance = LeftSplitContainer.SplitterDistance;


            //Если есть элементы в списке, то надо продумать их видимость и размеры
            if (CommandList != null)
            {
                //изменяем размеры каждого контола-команды
                foreach (Control contr in CommandsPanel.Controls)
                    contr.Width = contr.Height = Height - 
                        (contr.Margin.Top + contr.Margin.Bottom);

                RethinkVisibility();

                RethinkScroll();
            }
        }

        private void CommandsPanel_Paint(object sender, PaintEventArgs e)
        {
            //если нет команд, то мы ничего не рисуем
            if (CommandsPanel.Controls.Count == 0)
                return;

            var ctrl = CommandsPanel.Controls[0];

            //карандаш для рисования курсора и выделения
            var editpen = new Pen(Color.Coral,
                ctrl.Margin.Left + ctrl.Margin.Right);

            //ширина квадрата, рисуемого для выделения команды
            var width = ctrl.Width +
                (ctrl.Margin.Left + ctrl.Margin.Right);

            //координата, на которой должна рисоваться верхняя 
            //горизонтальная линия в квадрате выделения
            var top = ctrl.Margin.Top / 2;

            //координата, на которой должна рисоваться нижняя
            //горизонтальная линия в квадрате выделения
            var bottom = Height - top;

            //координата, на которой располагается курсор
            var editX = (editingTact - FirstVisibleTact) * width 
                + ctrl.Margin.Left/2 + CommandsPanel.Padding.Left;

            //рисуем курсор редактирования
            e.Graphics.DrawLine(
                editpen,
                new Point(editX, 0),
                new Point(editX, Height));

            //если данный СК редактируется в данный момент, то мы должны выделить команду.
            if ((editingTact < FirstVisibleTact + VisibleElementsCount) && (editingTact >= FirstVisibleTact))
            {
                if (isEditing)
                    e.Graphics.DrawLines(
                        editpen,
                        new Point[5]{ 
                        new Point(editX, top),
                        new Point(editX + width, top),
                        new Point(editX + width, bottom),
                        new Point(editX, bottom),
                        new Point(editX, top)
                    });
            }

            //рисуем границы области редактирования
            var borderpen = new Pen(Color.Black, 1);
            //левая граница
            e.Graphics.DrawLine(
                borderpen,
                new Point(0, 0),
                new Point(0, Height));
            //нижняя граница
            e.Graphics.DrawLine(
                borderpen,
                new Point(0, Height - 1),
                new Point(CommandsPanel.Width, Height - 1));
        }

        private void CommandList_OnCommandChanged(object sender, CommandSequenceChangedEventArgs e)
        {
            (CommandsPanel.Controls[e.TactNumber] as CommandControl)
                .SetCommand(CommandList.CommandSequence[e.TactNumber]);
        }

        private void CommandList_OnTactInserted(object sender, CommandSequenceChangedEventArgs e)
        {
            //создаём контрол, представляющий данную команду
            var newctrl = new CommandControl(CommandList.CommandSequence[e.TactNumber]);

            //добавляем команду и контрол в соответствующие списки
            CommandsPanel.Controls.InsertAtIndex(newctrl, e.TactNumber);

            RethinkScroll();
        }

        private void CommandList_OnTactDeleted(object sender, CommandSequenceChangedEventArgs e)
        {
            CommandsPanel.Controls.RemoveAt(e.TactNumber);

            if (CommandsPanel.Controls.Count == 0)
            {
                var pause = CommandList.CommandsScope.Find(com => com.Name == "Пауза");
                pause = pause.Clone();

                InsertTact(0, pause);
            }

            RethinkScroll();

            RethinkVisibility();
        }

        void CommandsPanel_ControlAdded(object sender, ControlEventArgs e)
        {
            e.Control.Height = e.Control.Width = Height -
                (e.Control.Margin.Top + e.Control.Margin.Bottom);

        }

        private void CommandsPanel_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                Parent.Focus();
        }
        #endregion
    }
}
